﻿using System;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Collections;
using System.Globalization;

namespace CMS_GYM
{
	/// <summary>	
	///	txtDate: cung cấp 3 loại định dạng Ngày tháng sử dụng phổ biến trên SCTVsoft: MM/yyyy, dd/MM/yyyy và dd/MM/yyyy HH:mm
    ///	Tác giả: Lương.HT
    ///	Ngày: 07/09/2012
	/// </summary>
	public partial class txtDate : TextBox
	{
        public enum typeDate
        {
            Month = 0,
            Date = 1,            
            DateHour = 2,
        }
        #region attributes
        private string m_format;		// display format (mask with input chars replaced by input char)
        private string m_convert;		// used to convert DateTime to string
        private char m_inpChar = '_';
        private string m_regex = @"[0-9]";
        private int m_caret;
        private Hashtable m_posNdx;
        private typeDate _DateFormat;
       
        // use MinValue as NullValue
		public static readonly DateTime NullValue = DateTime.MinValue;
        #endregion attributes
        // events
		public delegate void InvalidDateEventHandler(object sender, EventArgs e);
		public delegate void ValidDateEventHandler(object sender, EventArgs e);
        public delegate void typeDateChangeEventHandler(object sender, EventArgs em); 
        //public delegate void EventHandler(object sender, EventArgs e);

		public event InvalidDateEventHandler InvalidDate;
		public event ValidDateEventHandler ValidDate;
        public event typeDateChangeEventHandler typeDateChange;

        public txtDate()
		{
            InitializeComponent();
            //BuildFormat();
            ////this.m_format.Length = m_format.Length;
            //BuildPosNdx();
			//base.Text = m_format;

			// disable context menu since it bypasses Ctrl+V handler
			this.ContextMenu = new ContextMenu();
		}

        [Description("Định dạng chuỗi Thời gian. Là 1 trong 3 loại MM/yyyy, dd/MM/yyyy, dd/MM/yyyy HH:mm"), Category("Behavior")
        , RefreshProperties(RefreshProperties.All)]
        public typeDate dateFormat
        {
            get { return _DateFormat; }
            set 
            { 
                _DateFormat = value;
                BuildFormat();
                BuildPosNdx();
            }
        }

        [Category("Behavior")]
        protected void OntypeDateChange(EventArgs e)
        {
            if (typeDateChange != null)
                typeDateChange(this, e);
            BuildFormat();
        }

		[Category("Behavior")]
		protected void OnInvalidDate(EventArgs e)
		{
			if(InvalidDate != null)
				InvalidDate(this, e);
            //luong.ht
            //dValue = NullValue;
            this.BackColor = System.Drawing.Color.Red;
		}

		[Category("Behavior")]
		protected void OnValidDate(EventArgs e)
		{
			if(ValidDate != null)
				ValidDate(this, e);
            value = DateTime.ParseExact(this.Text, m_convert, new CultureInfo("es-ES"));
            this.BackColor = System.Drawing.SystemColors.Window;
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public override bool Multiline
		{
			get{return base.Multiline;}
			// ignore set
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public new int SelectionStart
		{
			get{return base.SelectionStart;}
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public override int SelectionLength
		{
			get{return base.SelectionLength;}
		}

		public override int MaxLength
		{
            get { return base.MaxLength; }
		}

		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
		{
			// NOTES: 
			//	1) break; causes warnings below
			//	2) m_caret tracks caret location, always the start of selected char
			//	3) code below is based on MaskedEdit, since our format is fixed
			//		there may be optimizations possible
			int strt = base.SelectionStart;
			int len = base.SelectionLength;
			int end = strt + base.SelectionLength - 1;
			string s = base.Text;
			int p;
			
			// handle startup, runs once
			if(m_format[strt] != m_inpChar)
			{
				strt = Next(-1);
				len = 1;
			}

			switch(keyData)
			{
				case Keys.Left:
				case Keys.Up:
					p = Prev(strt);
					if(p != strt)
					{
						base.SelectionStart = p;
						base.SelectionLength = 1;
					}
					m_caret = p;
					return true;
				case Keys.Left | Keys.Shift:
				case Keys.Up | Keys.Shift:
					if((strt < m_caret) || (strt == m_caret && len <= 1))
					{
						// enlarge left
						p = Prev(strt);
						base.SelectionStart -= (strt - p);
						base.SelectionLength = len + (strt - p);
					}
					else
					{
						// shrink right
						base.SelectionLength = len - (end - Prev(end));
					}
					return true;
				case Keys.Right:
				case Keys.Down:
					p = Next(strt);
					if(p != strt)
					{
						base.SelectionStart = p;
						base.SelectionLength = 1;
					}
					m_caret = p;
					return true;
				case Keys.Right | Keys.Shift:
				case Keys.Down | Keys.Shift:
					if(strt < m_caret)
					{
						// shrink left
						p = Next(strt);
						base.SelectionStart += (p - strt);
						base.SelectionLength = len - (p - strt);
					}
					else if(strt == m_caret)
					{
						// enlarge right
						p = Next(end);
						base.SelectionLength = len + (p - end);
					}
					return true;
				case Keys.Delete:
					// delete selection, replace with input format
					base.Text = s.Substring(0, strt) + m_format.Substring(strt, len) + s.Substring(strt + len);
					base.SelectionStart = strt;
					base.SelectionLength = 1;
					m_caret = strt;
					return true;
				case Keys.Home:
				case Keys.Left | Keys.Control:
				case Keys.Home | Keys.Control:
					base.SelectionStart = Next(-1);
					base.SelectionLength = 1;
					m_caret = base.SelectionStart;
					return true;
				case Keys.Home | Keys.Shift:
					if(strt <= m_caret && len <= 1)
					{
						// enlarge left
						p = Next(-1);
						base.SelectionStart -= (strt - p);
						base.SelectionLength = len + (strt - p);
					}
					else
					{
						// shrink right
						p = Next(-1);
						base.SelectionStart = p;
						base.SelectionLength = (m_caret - p) + 1;
					}
					return true;
				case Keys.End:
				case Keys.Right | Keys.Control:
				case Keys.End | Keys.Control:
					base.SelectionStart = Prev(this.m_format.Length);
					base.SelectionLength = 1;
					m_caret = base.SelectionStart;
					return true;
				case Keys.End | Keys.Shift:
					if(strt < m_caret)
					{
						// shrink left
						p = Prev(this.m_format.Length);
						base.SelectionStart = m_caret;
						base.SelectionLength = (p - m_caret + 1);
					}
					else if(strt == m_caret)
					{
						// enlarge right
						p = Prev(this.m_format.Length);
						base.SelectionLength = len + (p - end);
					}
					return true;
				case Keys.V | Keys.Control:
				case Keys.Insert | Keys.Shift:
					// paste from clipboard
					IDataObject iData = Clipboard.GetDataObject();

					// assemble new text
					string t = s.Substring(0, strt)
						+ (string)iData.GetData(DataFormats.Text)
						+ s.Substring(strt + len);

					// check if data to be pasted is convertable to inputType
					DateTime dt;;
					try
					{
                        dt = DateTime.ParseExact(t, m_convert, new CultureInfo("es-ES"));
						base.Text = dt.ToString(m_convert);

						// reset selection
						base.SelectionStart = strt;
						base.SelectionLength = len;
					}
					catch
					{
						// do nothing
					}
					
					return true;
				default:
					return base.ProcessCmdKey(ref msg, keyData);
			}
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			// reset selection to include input chars
			int strt = base.SelectionStart;
			int orig = strt;
			int len = base.SelectionLength;

			// reset selection start
			if(strt == this.m_format.Length || m_format[strt] != m_inpChar)
			{
				// reset start
				if(Next(strt) == strt)
					strt = Prev(strt);
				else
					strt = Next(strt);

				base.SelectionStart = strt;
			}

			// reset selection length
			if(len < 1)
				base.SelectionLength = 1;
			else if(m_format[orig + len - 1] != m_inpChar)
			{
				len += Next(strt + len) - (strt + len);
				base.SelectionLength = len;
			}

			m_caret = strt;
			base.OnMouseUp(e);
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
		public bool IsValid
		{
			get
			{
				try
				{
					// null is valid
					if(base.Text == m_format)
						return true;

                    this.value = DateTime.ParseExact(base.Text, m_convert, new CultureInfo("es-ES"));
				}
				catch
				{
                    this.value = NullValue;
					return false;
				}

				return true;
			}
		}        

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
		public DateTime value
		{
			get
			{
				// TODO: What to return if Null not allowed and invalid value?
				//	a) error?
				//	b) Null?
				DateTime ret;
				try
				{
                    ret = DateTime.ParseExact(base.Text, m_convert, new CultureInfo("es-ES"));
				}
				catch
				{
					ret = NullValue;
				}

				return ret;
			}
			set
			{
				if(value == NullValue)
					base.Text = m_format;
				else
					base.Text = value.ToString(m_convert);	// TODO: must format using current culture!!!

				//OnValidDate(EventArgs.Empty);
			}
		}

		protected override void OnLeave(EventArgs e)
		{
			// validate entry
			try
			{
                if (this.Text == m_format)
                {
                    this.BackColor = System.Drawing.SystemColors.Window;
                    return;
                }
                //DateTime dt1 = DateTime.Parse(this.Text);
                //6/15/2009 1:45:30 PM -> 15/06/2009 13:45 (es-ES)
                //6/15/2009 1:45:30 PM -> 15/06/2009 (fr-FR)

                DateTime dt = DateTime.ParseExact(this.Text, m_convert, new CultureInfo("es-ES"));
				OnValidDate(EventArgs.Empty);
			}
			catch
			{
				// fire InvalidDate event
				OnInvalidDate(EventArgs.Empty);
                this.Focus();
                return;
			}
			finally
			{
                //base.OnLeave(EventArgs.Empty);
			}
		}

		protected override void OnKeyPress(KeyPressEventArgs e)
		{
            try
            {
                int strt = base.SelectionStart;
                int len = base.SelectionLength;
                int p;

                // Handle Backspace -> replace previous char with inpchar and select
                if (e.KeyChar == 0x08)
                {
                    string s = base.Text;
                    p = Prev(strt);
                    if (p != strt)
                    {
                        base.Text = s.Substring(0, p) + m_inpChar.ToString() + s.Substring(p + 1);
                        base.SelectionStart = p;
                        base.SelectionLength = 1;

                    }
                    m_caret = p;
                    e.Handled = true;
                    return;
                }

                // update display if valid char entered
                if (IsValidChar(e.KeyChar, (int)m_posNdx[strt]))
                {
                    // assemble new text
                    string t = "";
                    t = base.Text.Substring(0, strt);
                    t += e.KeyChar.ToString();
                    //switch (t)
                    //{
                    //    case "4": t = "04"; break;
                    //    case "5": t = "05"; break;
                    //    case "6": t = "06"; break;
                    //    case "7": t = "07"; break;
                    //    case "8": t = "08"; break;
                    //    case "9": t = "09"; break;
                    //}
                    if (strt + len != this.m_format.Length)
                    {
                        t += m_format.Substring(strt + 1, len - 1);
                        t += base.Text.Substring(strt + len);
                    }
                    else
                        t += m_format.Substring(strt + 1);

                    base.Text = t;

                    // select next input char
                    strt = Next(strt);
                    base.SelectionStart = strt;
                    m_caret = strt;
                    base.SelectionLength = 1;
                }
                e.Handled = true;
            }
            catch (Exception) { }
		}

		private bool IsValidChar(char input, int pos)
		{
			// validate input char against mask
			return Regex.IsMatch(input.ToString(), m_regex);
		}

		private int Prev(int startPos)
		{
			// return previous input char position
			// returns current position if no input chars to the left
			int strt = startPos;
			int ret = strt;

			while(strt > 0)
			{
				strt--;
				if(m_format[strt] == m_inpChar)
					return strt;
			}
			return ret;			
		}

		private int Next(int startPos)
		{
			// return next input char position
			// returns current position if no input chars to the left
			int strt = startPos;
			int ret = strt;
			
			while(strt < this.m_format.Length - 1)
			{
				strt++;
				if(m_format[strt] == m_inpChar)
					return strt;
			}

			return ret;			
		}

		private void BuildFormat()
		{
            m_format = "";
            m_convert = "";
            string sep = "/";// DateTimeFormatInfo.CurrentInfo.DateSeparator;
            string sepH = ":";// DateTimeFormatInfo.CurrentInfo.TimeSeparator;
            string pat=string.Empty;
            int cont_compare=0;
            switch (dateFormat)
            {
                case typeDate.Month:
                    {
                        pat = "MM/yyyy";
                        cont_compare = 2;
                        //m_format = "__/____";
                        //m_convert = "MM/yyyy";
                        break;
                    }
                case typeDate.Date:
                    {
                        pat = "dd/MM/yyyy";
                        cont_compare = 3;
                        //m_format = "__/__/____";
                        //m_convert = "dd/MM/yyyy";
                        break;
                    }
                case typeDate.DateHour:
                    {
                        pat = "dd/MM/yyyy HH:mm";
                        cont_compare = 5;
                        break;
                    }
            }
                int pos = 0;
                string match = pat[pos].ToString();                    
                int cont = 0;
            
                while (cont < cont_compare)
                {
                    switch (match)
                    {
                        case "d":
                            m_format += m_inpChar.ToString() + m_inpChar.ToString() + sep;
                            m_convert += "dd" + sep;
                            break;
                        case "M":
                            m_format += m_inpChar.ToString() + m_inpChar.ToString() + sep;
                            m_convert += "MM" + sep;
                            break;
                        case "y":
                            m_format += m_inpChar.ToString() + m_inpChar.ToString() + m_inpChar.ToString() + m_inpChar.ToString() + " ";
                            m_convert += "yyyy" + " ";
                            break;
                        case "H":
                            m_format += m_inpChar.ToString() + m_inpChar.ToString() + sepH;
                            m_convert += "HH" + sepH;
                            break;
                        case "m":
                            m_format += m_inpChar.ToString() + m_inpChar.ToString();
                            m_convert += "mm";
                            break;
                        default:
                            break;
                    }

                    if (cont < cont_compare-1)// move to next Mdy char
                    {
                        pos = m_convert.Length;//pat.IndexOf(sep, pos) + 1;
                        match = pat[pos].ToString();
                    }
                    cont++;                
            }
            m_format = m_format.Trim();
            m_convert = m_convert.Trim();
            this.Text = m_format;
            this.MaxLength = m_format.Length;
		}

		private void BuildPosNdx()
		{
			// used to build position translation map from mask string
			//	and input format
			string s = m_format;

			// reset index
			if(m_posNdx == null)
				m_posNdx = new Hashtable();
			else
				m_posNdx.Clear();

			int cnt = 0;

			for(int i = 0; i < s.Length; i++)
			{
				if(s[i] == m_inpChar)
					m_posNdx.Add(cnt, i);

				cnt++;
			}
		}

        public void setNullValue()
        {
            base.Text = m_format;
            value = NullValue;
        }

        public bool IsNull()
        {
            try
            {
                if (base.Text == m_format || this.value == NullValue)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception) { return false; }
        }
	}
}
